iT邦幫忙

2026 iThome 鐵人賽

DAY 11
0
自我挑戰組

Data Engineer 下班後偷學 AI系列 第 11 篇

Milvus Hybrid Search:Weighted Ranker 與 RRF Ranker

  • 分享至 

  • xImage
  •  

透過 Dense Retrieval 和 Sparse Retrieval 我們可以拿到各自的 Top K,假設 Dense 和 Sparse 各自取 Top 10,我們難道就直接把最多 20 個候選結果全部回傳嗎?該如何做 ReRanking 呢?今天的文章中會介紹 Weighted Ranker 和 RRF Ranker 兩種 Reranking 方法,並示範如何在 Milvus 中使用 Hybrid Search。

Hybrid Search

Weighted Ranker

Dense 與 Sparse 各自有一個分數,直接給它們不同權重加起來做排序:
https://ithelp.ithome.com.tw/upload/images/20260925/20138939RcI8JIapFR.png

Dense 權重高一點就偏向語意一點,Sparse 權重高一點就偏向關鍵字一點。
但 Dense 算出來的分數跟 Sparse 算出來的分數,根本是不同的東西啊?舉例來說:

  • Dense 有 COSINE / IP / L2
  • Sparse 有 BM25 / IP

不同 metric 不能直接相加,所以相加前通常要先做 normalization,把原始 distance / score 轉成統一的 [0, 1] relevance score,以 Milvus 來說:

1. COSINE

COSINE 原本就在 [-1, 1] 之間,直接映射就好:

normalized = (1 + score) / 2

2. IP

IP 在 Milvus 中會用 arctan 方法:
https://ithelp.ithome.com.tw/upload/images/20260925/20138939yX1aJapSiG.png

arctan 是反正切函數(inverse tangent),也就是 tan 的反函數,可以把從 -∞ 到 +∞ 的數值映射進 -π/2 到 π/2 之間:
https://ithelp.ithome.com.tw/upload/images/20260925/201389391USY57pN1t.png

當然有很多其他函式可以做 normalize,只是 Milvus 選用 arctan。拿來跟標準的 sigmoid 做比較,能發現 arctan 尾端的收斂速度明顯比較慢,因此能在較大的 Score 區間保留更多差異。

套用回 IP 的 normalize 的公式,可以得到:

IP = -∞ → 接近 0
IP =  0 → 0.5
IP = +∞ → 接近 1

3. L2

L2 也是用 arctan,只是有些調整:
https://ithelp.ithome.com.tw/upload/images/20260925/20138939RgRAvpNHA6.png

L2 = 0   → 1
L2 = +∞  → 接近 0

4. BM25

Milvus 並沒有明確列出 BM25 的 normalize 方法,不過猜測大概會是類似這樣:
https://ithelp.ithome.com.tw/upload/images/20260925/20138939R2QeZyLzvR.png

Weighted Ranker Example

AnnSearchRequest 可以理解為一個還沒有真的執行 search() 的請求,而 hybrid_search() 吃的參數是 reqs: List[AnnSearchRequest],等於說我們是把請求和 ranker 傳進去,讓 Milvus 在背後幫我們處理實際的 searching 和 reranking。

from pymilvus import MilvusClient, AnnSearchRequest, Function, FunctionType

milvus_client = MilvusClient(uri="http://localhost:19530")

dense_search = AnnSearchRequest(
    data=[query_dense],
    anns_field="dense_vector",
    param={
        "metric_type": "COSINE",
        "params": {}
    },
    limit=20,
)

sparse_search = AnnSearchRequest(
    data=[query_sparse],
    anns_field="sparse_vector",
    param={
        "metric_type": "IP",
        "params": {}
    },
    limit=20,
)

weighted_ranker = Function(
    name="weighted_ranker",
    input_field_names=[],
    function_type=FunctionType.RERANK,
    params={
        "reranker": "weighted",
        "weights": [0.7, 0.3],
        "norm_score": True,
    }
)

results = milvus_client.hybrid_search(
    collection_name="documents",
    reqs=[
        dense_search,
        sparse_search,
    ],
    ranker=weighted_ranker,
    limit=5,
    output_fields=["text"],
)

RRF Ranker

直接用分數來加權真的是好的嗎?就算做過 normalization,如果某一路的 weight 或 score 長期比較佔優勢,最後的結果會不會仍然偏向其中一路?RRF 是 Reciprocal Rank Fusion,完全不看 score 只看原本各自的排名,公式:
https://ithelp.ithome.com.tw/upload/images/20260925/20138939VWq2tSZfTt.png

  • i 指的是第幾個 Retrieval,所以概念上是說:
    i = 1 → Dense Search
    i = 2 → Sparse Search
  • rank_i(doc) 代表文件在第 i 個搜尋結果中的排名,因此同一份文件如果在 Dense 和 Sparse Search 中都排得很前面,就會累積比較高的 RRF Score;反過來說,如果只在其中一路排名很前面,得到的加分就會比較有限。
  • k 可以理解成「Ranking 差距有多重要」,假設 k 很小,那第 1 名和第 10 名的分數會差很多;反過來 k 越大,不同排名之間的差距就會被壓縮。Milvus 目前預設 k=60,並建議在 [10,100] 中調整。

RRF 的好處是完全不需要在意 Dense、Sparse 原本使用什麼 Metric,也不用處理不同 Score 之間的尺度問題,只要知道各自的 Ranking 就能直接進行 Fusion。

RRF Ranker Example

把 ranker 改成 rrf。

rrf_ranker = Function(
    name="rrf_ranker",
    input_field_names=[],
    function_type=FunctionType.RERANK,
    params={
        "reranker": "rrf",
        "k": 60,
    }
)

results = milvus_client.hybrid_search(
    collection_name="documents",
    reqs=[
        dense_search,
        sparse_search,
    ],
    ranker=rrf_ranker,
    limit=5,
    output_fields=["text"],
)

每日一句

中秋節快樂。


上一篇
Milvus Sparse Retrieval:從 BM25 到 Sparse Vector
下一篇
Milvus Reranking:Decay Ranker、Boost Ranker 與 Model Ranker
系列文
Data Engineer 下班後偷學 AI 共 16 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言